From 005606bbfb8e13f231073e6f1fb5fe2e2498269f Mon Sep 17 00:00:00 2001 From: tsteven4 Date: Fri, 2 Nov 2018 07:55:31 -0600 Subject: [PATCH] support kml 2.3 track element. (#264) * support kml 2.3 track element. relax ordering requirement for gx:Track when and coord children. * fix kml includes and indentation. --- kml.cc | 135 ++++++++++++++-------- reference/track/Placemark-Track-1.kml | 21 ++++ reference/track/Placemark-Track-1~kml.gpx | 37 ++++++ testo.d/kml.test | 4 + 4 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 reference/track/Placemark-Track-1.kml create mode 100644 reference/track/Placemark-Track-1~kml.gpx diff --git a/kml.cc b/kml.cc index d0d1859ca..43858f339 100644 --- a/kml.cc +++ b/kml.cc @@ -26,15 +26,24 @@ #include "defs.h" #include "grtcirc.h" +#include "queue.h" +#include "src/core/datetime.h" #include "src/core/file.h" #include "src/core/xmlstreamwriter.h" #include "src/core/xmltag.h" #include "xmlgeneric.h" -#include -#include +#include // for QXmlStreamAttributes +#include // for QDateTime +#include // for QFile +#include // for QList +#include // for QString, QStringLiteral, QStat... +#include // for qint64, qPrintable +#include #include #include #include +#include +#include // options static char* opt_deficon = nullptr; @@ -73,6 +82,7 @@ static QString posnfilenametmp; static route_head* gx_trk_head; static QList* gx_trk_times; +static QList>* gx_trk_coords; static gpsbabel::File* oqfile; static gpsbabel::XmlStreamWriter* writer; @@ -276,6 +286,14 @@ xg_tag_mapping kml_map[] = { { gx_trk_e, cb_end, "/Placemark/*gx:Track" }, { gx_trk_when, cb_cdata, "/Placemark/*gx:Track/when" }, { gx_trk_coord, cb_cdata, "/Placemark/*gx:Track/gx:coord" }, + { gx_trk_s, cb_start, "/Placemark/Track" }, // KML 2.3 + { gx_trk_e, cb_end, "/Placemark/Track" }, // KML 2.3 + { gx_trk_when, cb_cdata, "/Placemark/Track/when" }, // KML 2.3 + { gx_trk_coord, cb_cdata, "/Placemark/Track/coord" }, // KML 2.3 + { gx_trk_s, cb_start, "/Placemark/MultiTrack/Track" }, // KML 2.3 + { gx_trk_e, cb_end, "/Placemark/MultiTrack/Track" }, // KML 2.3 + { gx_trk_when, cb_cdata, "/Placemark/MultiTrack/Track/when" }, // KML 2.3 + { gx_trk_coord, cb_cdata, "/Placemark/MultiTrack/Track/coord" }, // KML 2.3 { nullptr, (xg_cb_type) 0, nullptr } }; @@ -345,12 +363,12 @@ void wpt_time(xg_string args, const QXmlStreamAttributes*) void wpt_ts_begin(xg_string args, const QXmlStreamAttributes*) { - wpt_timespan_begin = xml_parse_time(args); + wpt_timespan_begin = xml_parse_time(args); } void wpt_ts_end(xg_string args, const QXmlStreamAttributes*) { - wpt_timespan_end = xml_parse_time(args); + wpt_timespan_end = xml_parse_time(args); } void wpt_coord(const QString& args, const QXmlStreamAttributes*) @@ -419,20 +437,20 @@ void trk_coord(xg_string args, const QXmlStreamAttributes*) * * If this is specified, then SetCreationDate */ - if ( wpt_timespan_begin.isValid() && wpt_timespan_end.isValid() ) { - - // If there are some Waypoints, then distribute the TimeSpan to all Waypoints - if ( trk_head->rte_waypt_ct > 0 ) { - qint64 timespan_ms = wpt_timespan_begin.msecsTo(wpt_timespan_end); - qint64 ms_per_waypoint = timespan_ms / trk_head->rte_waypt_ct; - queue* curr_elem; - queue* tmp_elem; - QUEUE_FOR_EACH(&trk_head->waypoint_list, curr_elem, tmp_elem) { - trkpt = reinterpret_cast(curr_elem); - trkpt->SetCreationTime(wpt_timespan_begin); - wpt_timespan_begin = wpt_timespan_begin.addMSecs(ms_per_waypoint); - } - } + if (wpt_timespan_begin.isValid() && wpt_timespan_end.isValid()) { + + // If there are some Waypoints, then distribute the TimeSpan to all Waypoints + if (trk_head->rte_waypt_ct > 0) { + qint64 timespan_ms = wpt_timespan_begin.msecsTo(wpt_timespan_end); + qint64 ms_per_waypoint = timespan_ms / trk_head->rte_waypt_ct; + queue* curr_elem; + queue* tmp_elem; + QUEUE_FOR_EACH(&trk_head->waypoint_list, curr_elem, tmp_elem) { + trkpt = reinterpret_cast(curr_elem); + trkpt->SetCreationTime(wpt_timespan_begin); + wpt_timespan_begin = wpt_timespan_begin.addMSecs(ms_per_waypoint); + } + } } } @@ -448,15 +466,51 @@ void gx_trk_s(xg_string, const QXmlStreamAttributes*) track_add_head(gx_trk_head); delete gx_trk_times; gx_trk_times = new QList; + delete gx_trk_coords; + gx_trk_coords = new QList>; } void gx_trk_e(xg_string, const QXmlStreamAttributes*) { + // Check that for every temporal value (kml:when) in a kml:Track there is a position (kml:coord) value. + // Check that for every temporal value (kml:when) in a gx:Track there is a position (gx:coord) value. + if (gx_trk_times->size() != gx_trk_coords->size()) { + fatal(MYNAME ": There were more coord elements than the number of when elements.\n"); + } + + // In KML 2.3 kml:Track elements kml:coord and kml:when elements are not required to be in any order. + // In gx:Track elements all kml:when elements are required to precede all gx:coord elements. + // For both we allow any order. Many writers using gx:Track elements don't adhere to the schema. + while (!gx_trk_times->isEmpty()) { + Waypoint* trkpt = new Waypoint; + trkpt->SetCreationTime(gx_trk_times->takeFirst()); + double lat, lon, alt; + int n; + std::tie(n, lat, lon, alt) = gx_trk_coords->takeFirst(); + // An empty kml:coord element is permitted to indicate missing position data; + // the estimated position may be determined using some interpolation method. + // However if we get one we will throw away the time as we don't have a location. + // It is not clear that coord elements without altitude are allowed, but our + // writer produces them. + if (n >= 2) { + trkpt->latitude = lat; + trkpt->longitude = lon; + if (n >= 3) { + trkpt->altitude = alt; + } + track_add_wpt(gx_trk_head, trkpt); + } else { + delete trkpt; + } + } + if (!gx_trk_head->rte_waypt_ct) { track_del_head(gx_trk_head); } delete gx_trk_times; gx_trk_times = nullptr; + delete gx_trk_coords; + gx_trk_coords = nullptr; } void gx_trk_when(xg_string args, const QXmlStreamAttributes*) @@ -469,31 +523,16 @@ void gx_trk_when(xg_string args, const QXmlStreamAttributes*) void gx_trk_coord(xg_string args, const QXmlStreamAttributes*) { - double lat, lon, alt; - - if (! gx_trk_times || gx_trk_times->isEmpty()) { - fatal(MYNAME ": There were more gx:coord elements than the number of when elements.\n"); + if (! gx_trk_coords) { + fatal(MYNAME ": gx_trk_coord: invalid kml file\n"); } - Waypoint* trkpt = new Waypoint; - trkpt->SetCreationTime(gx_trk_times->takeFirst()); + + double lat, lon, alt; int n = sscanf(CSTR(args), "%lf %lf %lf", &lon, &lat, &alt); - // Empty gx_coord elements are allowed to balance the number of when elements, - // but if we get one we will throw away the time as we don't have a location. - // It is not clear that coord elements without altitude are allowed, but our - // writer produces them. if (0 != n && 2 != n && 3 != n) { - fatal(MYNAME ": gx:coord field decode failure on \"%s\".\n", qPrintable(args)); - } - if (n >= 2) { - trkpt->latitude = lat; - trkpt->longitude = lon; - if (n >= 3) { - trkpt->altitude = alt; - } - track_add_wpt(gx_trk_head, trkpt); - } else { - delete trkpt; + fatal(MYNAME ": coord field decode failure on \"%s\".\n", qPrintable(args)); } + gx_trk_coords->append(std::make_tuple(n, lat, lon, alt)); } static @@ -1059,7 +1098,7 @@ static void kml_output_tailer(const route_head* header) queue* elem, *tmp; QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* tpt = reinterpret_cast(elem); + Waypoint* tpt = reinterpret_cast(elem); int first_in_trk = tpt->Q.prev == &header->waypoint_list; if (!first_in_trk && tpt->wpt_flags.new_trkseg) { needs_multigeometry = 1; @@ -1096,7 +1135,7 @@ static void kml_output_tailer(const route_head* header) } QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* tpt = reinterpret_cast(elem); + Waypoint* tpt = reinterpret_cast(elem); int first_in_trk = tpt->Q.prev == &header->waypoint_list; if (tpt->wpt_flags.new_trkseg) { if (!first_in_trk) { @@ -1684,7 +1723,7 @@ static void kml_mt_simple_array(const route_head* header, QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* wpt = reinterpret_cast(elem); + Waypoint* wpt = reinterpret_cast(elem); switch (member) { case fld_power: @@ -1715,7 +1754,7 @@ static int track_has_time(const route_head* header) queue* elem, *tmp; int points_with_time = 0; QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* tpt = reinterpret_cast(elem); + Waypoint* tpt = reinterpret_cast(elem); if (tpt->GetCreationTime().isValid()) { points_with_time++; @@ -1733,7 +1772,7 @@ static void write_as_linestring(const route_head* header) queue* elem, *tmp; kml_track_hdr(header); QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* tpt = reinterpret_cast(elem); + Waypoint* tpt = reinterpret_cast(elem); kml_track_disp(tpt); } kml_track_tlr(header); @@ -1763,7 +1802,7 @@ static void kml_mt_hdr(const route_head* header) kml_output_positioning(false); QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* tpt = reinterpret_cast(elem); + Waypoint* tpt = reinterpret_cast(elem); if (tpt->GetCreationTime().isValid()) { QString time_string = tpt->CreationTimeXML(); @@ -1776,7 +1815,7 @@ static void kml_mt_hdr(const route_head* header) // TODO: How to handle clamped, floating, extruded, etc.? QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) { - Waypoint* tpt = reinterpret_cast(elem); + Waypoint* tpt = reinterpret_cast(elem); if (kml_altitude_known(tpt)) { writer->writeTextElement(QStringLiteral("gx:coord"), @@ -2153,7 +2192,7 @@ kml_wr_position(Waypoint* wpt) track points if we've not moved a minimum distance from the beginning of our accumulated track. */ { - Waypoint* newest_posn= reinterpret_castQUEUE_LAST(&posn_trk_head->waypoint_list); + Waypoint* newest_posn= reinterpret_castQUEUE_LAST(&posn_trk_head->waypoint_list); if (radtometers(gcdist(RAD(wpt->latitude), RAD(wpt->longitude), RAD(newest_posn->latitude), RAD(newest_posn->longitude))) > 50) { @@ -2178,7 +2217,7 @@ kml_wr_position(Waypoint* wpt) */ while (max_position_points && (posn_trk_head->rte_waypt_ct >= max_position_points)) { - Waypoint* tonuke = reinterpret_castQUEUE_FIRST(&posn_trk_head->waypoint_list); + Waypoint* tonuke = reinterpret_castQUEUE_FIRST(&posn_trk_head->waypoint_list); track_del_wpt(posn_trk_head, tonuke); } diff --git a/reference/track/Placemark-Track-1.kml b/reference/track/Placemark-Track-1.kml new file mode 100644 index 000000000..85cd1b0b2 --- /dev/null +++ b/reference/track/Placemark-Track-1.kml @@ -0,0 +1,21 @@ + + + + + 2010-05-28T02:02:09Z + 2010-05-28T02:02:35Z + 2010-05-28T02:02:44Z + 2010-05-28T02:02:53Z + 2010-05-28T02:02:54Z + 2010-05-28T02:02:55Z + 2010-05-28T02:02:56Z + -122.207881 37.371915 156.000000 + -122.205712 37.373288 152.000000 + -122.204678 37.373939 147.000000 + -122.203572 37.374630 142.199997 + -122.203451 37.374706 141.800003 + -122.203329 37.374780 141.199997 + -122.203207 37.374857 140.199997 + + + diff --git a/reference/track/Placemark-Track-1~kml.gpx b/reference/track/Placemark-Track-1~kml.gpx new file mode 100644 index 000000000..7862726ae --- /dev/null +++ b/reference/track/Placemark-Track-1~kml.gpx @@ -0,0 +1,37 @@ + + + + + + + + 156.000 + + + + 152.000 + + + + 147.000 + + + + 142.200 + + + + 141.800 + + + + 141.200 + + + + 140.200 + + + + + diff --git a/testo.d/kml.test b/testo.d/kml.test index 77d64ac20..55b3c7434 100644 --- a/testo.d/kml.test +++ b/testo.d/kml.test @@ -61,6 +61,10 @@ compare ${REFERENCE}/bounds-test.kml ${TMPDIR}/bnds_so.kml gpsbabel -i gpx -f ${REFERENCE}/track/tracks.gpx -o kml,track=1,trackdirection=1,units='m' -F ${TMPDIR}/tracks~gpx.kml compare ${REFERENCE}/track/tracks~gpx.kml ${TMPDIR}/tracks~gpx.kml +# kml 2.3 +gpsbabel -i kml -f ${REFERENCE}/track/Placemark-Track-1.kml -o gpx -F ${TMPDIR}/Placemark-Track-1~kml.gpx +compare ${REFERENCE}/track/Placemark-Track-1~kml.gpx ${TMPDIR}/Placemark-Track-1~kml.gpx + set -e if which xmllint > /dev/null; then -- 2.30.2